/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2012
 *					All rights reserved
 *
 *  This file is part of GPAC / Media terminal sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */



#include <gpac/internal/terminal_dev.h>
#include "media_memory.h"
#include <gpac/internal/compositor_dev.h>


u32 MM_Loop(void *par);


enum
{
	GF_MM_CE_RUNNING= 1,
	GF_MM_CE_HAS_ERROR = 1<<1,
	GF_MM_CE_THREADED = 1<<2,
	GF_MM_CE_REQ_THREAD = 1<<3,
	/*only used by threaded decs to signal end of thread*/
	GF_MM_CE_DEAD = 1<<4,
	GF_MM_CE_DISCARDED = 1<<5,
};

typedef struct
{
	u32 flags;
	GF_Codec *dec;
	/*for threaded decoders*/
	GF_Thread *thread;
	GF_Mutex *mx;
} CodecEntry;

GF_Err gf_term_init_scheduler(GF_Terminal *term, u32 threading_mode)
{
	term->mm_mx = gf_mx_new("MediaManager");
	term->codecs = gf_list_new();

	term->frame_duration = 33;
	switch (threading_mode) {
	case GF_TERM_THREAD_SINGLE:
		term->flags |= GF_TERM_SINGLE_THREAD;
		break;
	case GF_TERM_THREAD_MULTI:
		term->flags |= GF_TERM_MULTI_THREAD;
		break;
	default:
		break;
	}

	if (term->user->init_flags & GF_TERM_NO_DECODER_THREAD)
		return GF_OK;

	term->mm_thread = gf_th_new("MediaManager");
	term->flags |= GF_TERM_RUNNING;
	term->priority = GF_THREAD_PRIORITY_NORMAL;
	gf_th_run(term->mm_thread, MM_Loop, term);
	return GF_OK;
}

void gf_term_stop_scheduler(GF_Terminal *term)
{
	if (term->mm_thread) {
		u32 count, i;

		term->flags &= ~GF_TERM_RUNNING;
		while (!(term->flags & GF_TERM_DEAD) )
			gf_sleep(2);

		count = gf_list_count(term->codecs);
		for (i=0; i<count; i++) {
			CodecEntry *ce = gf_list_get(term->codecs, i);
			if (ce->flags & GF_MM_CE_DISCARDED) {
				gf_free(ce);
				gf_list_rem(term->codecs, i);
				count--;
				i--;
			}
		}

		assert(! gf_list_count(term->codecs));
		gf_th_del(term->mm_thread);
	}
	gf_list_del(term->codecs);
	gf_mx_del(term->mm_mx);
}

static CodecEntry *mm_get_codec(GF_List *list, GF_Codec *codec)
{
	CodecEntry *ce;
	u32 i = 0;
	while ((ce = (CodecEntry*)gf_list_enum(list, &i))) {
		if (ce->dec==codec) return ce;
	}
	return NULL;
}


void gf_term_add_codec(GF_Terminal *term, GF_Codec *codec)
{
	u32 i, count;
	Bool threaded;
	CodecEntry *cd;
	CodecEntry *ptr, *next;
	GF_CodecCapability cap;
	assert(codec);

	GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Terminal] Registering codec %s\n", codec->decio ? codec->decio->module_name : "RAW"));

	/*caution: the mutex can be grabbed by a decoder waiting for a mutex owned by the calling thread
	this happens when several scene codecs are running concurently and triggering play/pause on media*/
	gf_mx_p(term->mm_mx);

	cd = mm_get_codec(term->codecs, codec);
	if (cd) goto exit;

	GF_SAFEALLOC(cd, CodecEntry);
	if (!cd) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Terminal] Failed to allocate decoder entry\n"));
		return;
	}
	cd->dec = codec;
	if (!cd->dec->Priority)
		cd->dec->Priority = 1;

	/*we force audio codecs to be threaded in free mode, so that we avoid waiting in the audio renderer if another decoder is locking the main mutex
	this can happen when the audio decoder is running late*/
	if (codec->type==GF_STREAM_AUDIO) {
		threaded = 1;
	} else {
		cap.CapCode = GF_CODEC_WANTS_THREAD;
		cap.cap.valueInt = 0;
		gf_codec_get_capability(codec, &cap);
		threaded = cap.cap.valueInt;
	}

	if (threaded) cd->flags |= GF_MM_CE_REQ_THREAD;


	if (term->flags & GF_TERM_MULTI_THREAD) {
		if ((codec->type==GF_STREAM_AUDIO) || (codec->type==GF_STREAM_VISUAL)) threaded = 1;
	} else if (term->flags & GF_TERM_SINGLE_THREAD) {
		threaded = 0;
	}
	if (codec->flags & GF_ESM_CODEC_IS_RAW_MEDIA)
		threaded = 0;

	if (threaded) {
		cd->thread = gf_th_new(cd->dec->decio->module_name);
		cd->mx = gf_mx_new(cd->dec->decio->module_name);
		cd->flags |= GF_MM_CE_THREADED;
		gf_list_add(term->codecs, cd);
		goto exit;
	}

	//add codec 1- per priority 2- per type, audio being first
	//priorities inherits from Systems (5bits) so range from 0 to 31
	//we sort from MAX to MIN
	count = gf_list_count(term->codecs);
	for (i=0; i<count; i++) {
		ptr = (CodecEntry*)gf_list_get(term->codecs, i);
		if (ptr->flags & GF_MM_CE_THREADED) continue;

		//higher priority, continue
		if (ptr->dec->Priority > codec->Priority) continue;

		//same priority, put audio first
		if (ptr->dec->Priority == codec->Priority) {
			//we insert audio (0x05) before video (0x04)
			if (ptr->dec->type < codec->type) {
				gf_list_insert(term->codecs, cd, i);
				goto exit;
			}
			//same prior, same type: insert after
			if (ptr->dec->type == codec->type) {
				if (i+1==count) {
					gf_list_add(term->codecs, cd);
				} else {
					gf_list_insert(term->codecs, cd, i+1);
				}
				goto exit;
			}
			//we insert video (0x04) after audio (0x05) if next is not audio
			//last one
			if (i+1 == count) {
				gf_list_add(term->codecs, cd);
				goto exit;
			}
			next = (CodecEntry*)gf_list_get(term->codecs, i+1);
			//# priority level, insert
			if ((next->flags & GF_MM_CE_THREADED) || (next->dec->Priority != codec->Priority)) {
				gf_list_insert(term->codecs, cd, i+1);
				goto exit;
			}
			//same priority level and at least one after : continue
			continue;
		}
		gf_list_insert(term->codecs, cd, i);
		goto exit;
	}
	//if we got here, first in list
	gf_list_add(term->codecs, cd);

exit:
	gf_mx_v(term->mm_mx);
	return;
}

void gf_term_remove_codec(GF_Terminal *term, GF_Codec *codec)
{
	u32 i;
	Bool locked;
	CodecEntry *ce;

	GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Terminal] Unregistering codec %s\n", codec->decio ? codec->decio->module_name : "RAW"));

	/*cf note above*/
	locked = gf_mx_try_lock(term->mm_mx);

	i=0;
	while ((ce = (CodecEntry*)gf_list_enum(term->codecs, &i))) {
		if (ce->dec != codec) continue;

		if (ce->thread) {
			if (ce->flags & GF_MM_CE_RUNNING) {
				ce->flags &= ~GF_MM_CE_RUNNING;
				while (! (ce->flags & GF_MM_CE_DEAD)) gf_sleep(10);
				ce->flags &= ~GF_MM_CE_DEAD;
			}
			gf_th_del(ce->thread);
			gf_mx_del(ce->mx);
		}
		if (locked) {
			gf_free(ce);
			gf_list_rem(term->codecs, i-1);
		} else {
			ce->flags |= GF_MM_CE_DISCARDED;
		}
		break;
	}
	if (locked) gf_mx_v(term->mm_mx);
	return;
}

Bool gf_term_find_codec(GF_Terminal *term, GF_Codec *codec)
{
	CodecEntry *ce;
	u32 i=0;
	while ((ce = (CodecEntry*)gf_list_enum(term->codecs, &i))) {
		if (ce->dec == codec) return 1;
	}
	return 0;
}

static void MM_handleServices(GF_Terminal *term)
{
#ifndef GF_DISABLE_LOG
	term->compositor->networks_time = gf_sys_clock();
#endif

//	GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[Media Manager] Entering simultion step\n"));
	gf_term_handle_services(term);

#ifndef GF_DISABLE_LOG
	term->compositor->networks_time = gf_sys_clock() - term->compositor->networks_time;
#endif

}

static u32 MM_SimulationStep_Decoder(GF_Terminal *term, u32 *nb_active_decs)
{
	CodecEntry *ce;
	GF_Err e;
	u32 count, remain;
	u32 time_taken, time_slice, time_left;

#ifndef GF_DISABLE_LOG
	term->compositor->decoders_time = gf_sys_clock();
#endif
	gf_mx_p(term->mm_mx);

	count = gf_list_count(term->codecs);
	time_left = term->frame_duration;
	*nb_active_decs = 0;

	if (term->last_codec >= count) term->last_codec = 0;
	remain = count;
	/*this is ultra basic a nice scheduling system would be much better*/
	while (remain) {
		ce = (CodecEntry*)gf_list_get(term->codecs, term->last_codec);
		if (!ce) break;

		if (!(ce->flags & GF_MM_CE_RUNNING) || (ce->flags & GF_MM_CE_THREADED) || ce->dec->force_cb_resize) {
			remain--;
			if (!remain) break;
			term->last_codec = (term->last_codec + 1) % count;
			continue;
		}
		time_slice = ce->dec->Priority * time_left / term->cumulated_priority;
		if (ce->dec->PriorityBoost) time_slice *= 2;
		time_taken = gf_sys_clock();
		(*nb_active_decs) ++;
		e = gf_codec_process(ce->dec, time_slice);
		time_taken = gf_sys_clock() - time_taken;
		/*avoid signaling errors too often...*/
#ifndef GPAC_DISABLE_LOG
		if (e) {
			GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[ODM%d] Decoding Error %s\n", ce->dec->odm->OD->objectDescriptorID, gf_error_to_string(e) ));
		} else {
			//GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[%s] Decode time slice %d ms out of %d ms\n", ce->dec->decio ? ce->dec->decio->module_name : "RAW", time_taken, time_left ));
		}
#endif
		if (ce->flags & GF_MM_CE_DISCARDED) {
			gf_free(ce);
			gf_list_rem(term->codecs, term->last_codec);
			count--;
			if (!count)
				break;
		} else {
			if (ce->dec->CB && (ce->dec->CB->UnitCount >= ce->dec->CB->Min)) ce->dec->PriorityBoost = 0;
		}
		term->last_codec = (term->last_codec + 1) % count;

		remain -= 1;
		if (time_left > time_taken) {
			time_left -= time_taken;
			if (!remain) break;
		} else {
			time_left = 0;
			break;
		}
	}
	gf_mx_v(term->mm_mx);
#ifndef GF_DISABLE_LOG
	term->compositor->decoders_time = gf_sys_clock() - term->compositor->decoders_time;
#endif

	return time_left;
}

u32 MM_Loop(void *par)
{
	GF_Terminal *term = (GF_Terminal *) par;
	Bool do_scene = (term->flags & GF_TERM_NO_VISUAL_THREAD) ? 1 : 0;
	Bool do_codec = (term->flags & GF_TERM_NO_DECODER_THREAD) ? 0 : 1;
	Bool do_regulate = (term->user->init_flags & GF_TERM_NO_REGULATION) ? 0 : 1;
	Bool no_compositor_thread = (term->user->init_flags & GF_TERM_NO_COMPOSITOR_THREAD) ? 1 : 0;

	gf_th_set_priority(term->mm_thread, term->priority);
	GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[MediaManager] Entering thread ID %d\n", gf_th_id() ));
//	GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("(RTI] Terminal Cycle Log\tServices\tDecoders\tCompositor\tSleep\n"));

	while (term->flags & GF_TERM_RUNNING) {
		u32 nb_decs = 0;
		u32 left = 0;

		if (!no_compositor_thread) 
			MM_handleServices(term);

		if (do_codec) left = MM_SimulationStep_Decoder(term, &nb_decs);
		else left = term->frame_duration;

		if (do_scene) {
			s32 ms_until_next=0;
			u32 time_taken = gf_sys_clock();
			gf_sc_draw_frame(term->compositor, 0, &ms_until_next);
			time_taken = gf_sys_clock() - time_taken;
			if (ms_until_next< (s32) term->frame_duration/2) {
				left = 0;
			} else if (left>time_taken)
				left -= time_taken;
			else
				left = 0;
		}
		if (do_regulate) {
			if (term->bench_mode) {
				gf_sleep(0);
			} else {
				if (left==term->frame_duration) {
					//if nothing was done during this pass but we have active decoder, just yield. We don't want to sleep since
					//composition memory could be released at any time. We should have a signal here, rather than a wait
					gf_sleep(nb_decs ? 0 : term->frame_duration/2);
				}
			}
		}
	}
	term->flags |= GF_TERM_DEAD;
	return 0;
}

u32 RunSingleDec(void *ptr)
{
	GF_Err e;
	u64 time_taken;
	CodecEntry *ce = (CodecEntry *) ptr;

	GF_LOG(GF_LOG_DEBUG, GF_LOG_CORE, ("[MediaDecoder %d] Entering thread ID %d\n", ce->dec->odm->OD->objectDescriptorID, gf_th_id() ));

	while (ce->flags & GF_MM_CE_RUNNING) {
		time_taken = gf_sys_clock_high_res();
		if (!ce->dec->force_cb_resize) {
			gf_mx_p(ce->mx);
			e = gf_codec_process(ce->dec, ce->dec->odm->term->frame_duration);
			if (e) gf_term_message(ce->dec->odm->term, ce->dec->odm->net_service->url, "Decoding Error", e);
			gf_mx_v(ce->mx);
		}
		time_taken = gf_sys_clock_high_res() - time_taken;


		/*no priority boost this way for systems codecs, priority is dynamically set by not releasing the
		graph when late and moving on*/
		if (!ce->dec->CB || (ce->dec->CB->UnitCount == ce->dec->CB->Capacity))
			ce->dec->PriorityBoost = 0;

		/*while on don't sleep*/
		if (ce->dec->PriorityBoost) continue;

		if (time_taken<20) {
			gf_sleep(1);
		}
	}
	ce->flags |= GF_MM_CE_DEAD;
	return 0;
}

/*NOTE: when starting/stopping a decoder we only lock the decoder mutex, NOT the media manager. This
avoids deadlocking in case a system codec waits for the scene graph and the compositor requests
a stop/start on a media*/
void gf_term_start_codec(GF_Codec *codec, Bool is_resume)
{
	GF_CodecCapability cap;
	CodecEntry *ce;
	GF_Terminal *term = codec->odm->term;
	if (!gf_list_count(codec->odm->channels)) return;
	ce = mm_get_codec(term->codecs, codec);
	if (!ce) return;

	/*lock dec*/
	if (ce->mx) gf_mx_p(ce->mx);

	/*clean decoder memory and wait for RAP*/
	if (!is_resume && codec->CB) gf_cm_reset(codec->CB);

	if (!is_resume) {
		cap.CapCode = GF_CODEC_WAIT_RAP;
		gf_codec_set_capability(codec, cap);

		if (codec->decio && (codec->decio->InterfaceType == GF_SCENE_DECODER_INTERFACE)) {
			cap.CapCode = GF_CODEC_SHOW_SCENE;
			cap.cap.valueInt = 1;
			gf_codec_set_capability(codec, cap);
		}
	}

	gf_codec_set_status(codec, GF_ESM_CODEC_PLAY);

	if (!(ce->flags & GF_MM_CE_RUNNING)) {
		ce->flags |= GF_MM_CE_RUNNING;
		if (ce->thread) {
			gf_th_run(ce->thread, RunSingleDec, ce);
			gf_th_set_priority(ce->thread, term->priority);
		} else {
			term->cumulated_priority += ce->dec->Priority+1;
		}
	}


	/*unlock dec*/
	if (ce->mx)
		gf_mx_v(ce->mx);
}

void gf_term_stop_codec(GF_Codec *codec, u32 reason)
{
	GF_CodecCapability cap;
	Bool locked = 0;
	CodecEntry *ce;
	GF_Terminal *term = codec->odm->term;
	ce = mm_get_codec(term->codecs, codec);
	if (!ce) return;

	if (ce->mx) gf_mx_p(ce->mx);
	/*We must make sure:
		1- media codecs are synchrounously stop otherwise we could destroy the composition memory while
	the codec writes to it
		2- prevent deadlock for other codecs waiting for the scene graph
	*/
	else if (codec->CB) {
		locked = 1;
		gf_mx_p(term->mm_mx);
	} else {
		locked = gf_mx_try_lock(term->mm_mx);
	}

	/*for audio codec force CB to stop state to discard any pending AU. Not doing so would lead to a wrong estimation of the clock drift
	when resuming the object*/
	if (codec->type==GF_STREAM_AUDIO) {
		if (reason != 2) {
			gf_codec_set_status(codec, GF_ESM_CODEC_STOP);
		}
	}
	//if video is in a dynamic scene, reset the CB if user stop (eg codec was not in EOS). Otherwise (bifs,svg) we may want to keep the last decoded image
	else if ((codec->Status<GF_ESM_CODEC_EOS) && codec->odm && codec->odm->parentscene && codec->odm->parentscene->is_dynamic_scene && codec->CB && (codec->CB->Capacity>1)) {
		gf_codec_set_status(codec, GF_ESM_CODEC_STOP);
	}
	/*otherwise set status directly and don't touch CB state*/
	else {
		codec->Status = GF_ESM_CODEC_STOP;
	}

	if ((reason==2) && codec->CB) {
		gf_cm_set_eos(codec->CB);
	}

	/*signal the codec we stopped*/
	if (reason == 0) {
		cap.CapCode = GF_CODEC_ABORT;
		cap.cap.valueInt = 0;
		gf_codec_set_capability(codec, cap);

		if (codec->decio && codec->odm->mo && (codec->odm->mo->flags & GF_MO_DISPLAY_REMOVE) ) {
			cap.CapCode = GF_CODEC_SHOW_SCENE;
			cap.cap.valueInt = 0;
			gf_codec_set_capability(codec, cap);
			codec->odm->mo->flags &= ~GF_MO_DISPLAY_REMOVE;
		}
	}

	/*don't wait for end of thread since this can be triggered within the decoding thread*/
	if (ce->flags & GF_MM_CE_RUNNING) {
		ce->flags &= ~GF_MM_CE_RUNNING;
		if (!ce->thread)
			term->cumulated_priority -= codec->Priority+1;
	}
	if (codec->CB) gf_cm_abort_buffering(codec->CB);

	if (ce->mx) gf_mx_v(ce->mx);
	/*cf note above*/
	else if (locked) gf_mx_v(term->mm_mx);
}

void gf_term_set_threading(GF_Terminal *term, u32 mode)
{
	u32 i;
	Bool thread_it, restart_it;
	CodecEntry *ce;

	switch (mode) {
	case GF_TERM_THREAD_SINGLE:
		if (term->flags & GF_TERM_SINGLE_THREAD) return;
		term->flags &= ~GF_TERM_MULTI_THREAD;
		term->flags |= GF_TERM_SINGLE_THREAD;
		break;
	case GF_TERM_THREAD_MULTI:
		if (term->flags & GF_TERM_MULTI_THREAD) return;
		term->flags &= ~GF_TERM_SINGLE_THREAD;
		term->flags |= GF_TERM_MULTI_THREAD;
		break;
	default:
		if (!(term->flags & (GF_TERM_MULTI_THREAD | GF_TERM_SINGLE_THREAD) ) ) return;
		term->flags &= ~GF_TERM_SINGLE_THREAD;
		term->flags &= ~GF_TERM_MULTI_THREAD;
		break;
	}

	gf_mx_p(term->mm_mx);


	i=0;
	while ((ce = (CodecEntry*)gf_list_enum(term->codecs, &i))) {
		thread_it = 0;
		/*free mode, decoder wants threading - do */
		if ((mode == GF_TERM_THREAD_FREE) && (ce->flags & GF_MM_CE_REQ_THREAD)) thread_it = 1;
		else if (mode == GF_TERM_THREAD_MULTI) thread_it = 1;

		if (thread_it && (ce->flags & GF_MM_CE_THREADED)) continue;
		if (!thread_it && !(ce->flags & GF_MM_CE_THREADED)) continue;

		restart_it = 0;
		if (ce->flags & GF_MM_CE_RUNNING) {
			restart_it = 1;
			ce->flags &= ~GF_MM_CE_RUNNING;
		}

		if (ce->flags & GF_MM_CE_THREADED) {
			/*wait for thread to die*/
			while (!(ce->flags & GF_MM_CE_DEAD)) gf_sleep(1);
			ce->flags &= ~GF_MM_CE_DEAD;
			gf_th_del(ce->thread);
			ce->thread = NULL;
			gf_mx_del(ce->mx);
			ce->mx = NULL;
			ce->flags &= ~GF_MM_CE_THREADED;
		} else {
			term->cumulated_priority -= ce->dec->Priority+1;
		}

		if (thread_it) {
			ce->flags |= GF_MM_CE_THREADED;
			ce->thread = gf_th_new(ce->dec->decio->module_name);
			ce->mx = gf_mx_new(ce->dec->decio->module_name);
		}

		if (restart_it) {
			ce->flags |= GF_MM_CE_RUNNING;
			if (ce->thread) {
				gf_th_run(ce->thread, RunSingleDec, ce);
				gf_th_set_priority(ce->thread, term->priority);
			} else {
				term->cumulated_priority += ce->dec->Priority+1;
			}
		}
	}
	gf_mx_v(term->mm_mx);
}

Bool gf_term_lock_codec(GF_Codec *codec, Bool lock, Bool trylock)
{
	Bool res = 1;
	CodecEntry *ce;
	GF_Terminal *term = codec->odm->term;

	ce = mm_get_codec(term->codecs, codec);
	if (!ce) return 0;

	if (ce->mx) {
		if (lock) {
			if (trylock) {
				res = gf_mx_try_lock(ce->mx);
			} else {
				res = gf_mx_p(ce->mx);
			}
		}
		else gf_mx_v(ce->mx);
	}
	else {
		if (lock) {
			if (trylock) {
				res = gf_mx_try_lock(term->mm_mx);
			} else {
				res = gf_mx_p(term->mm_mx);
			}
		}
		else gf_mx_v(term->mm_mx);
	}
	return res;
}

void gf_term_set_priority(GF_Terminal *term, s32 Priority)
{
	u32 i;
	CodecEntry *ce;
	gf_mx_p(term->mm_mx);

	gf_th_set_priority(term->mm_thread, Priority);

	i=0;
	while ((ce = (CodecEntry*)gf_list_enum(term->codecs, &i))) {
		if (ce->flags & GF_MM_CE_THREADED)
			gf_th_set_priority(ce->thread, Priority);
	}
	term->priority = Priority;
	gf_mx_v(term->mm_mx);
}

GF_EXPORT
u32 gf_term_process_step(GF_Terminal *term)
{
	u32 nb_decs=0;
	u32 sleep_time=0;
	u32 dec_time = 0, step_start_time = gf_sys_clock();

	MM_handleServices(term);

	if (term->flags & GF_TERM_NO_DECODER_THREAD) {
		MM_SimulationStep_Decoder(term, &nb_decs);
		dec_time = gf_sys_clock() - step_start_time;
	}

	if (term->flags & GF_TERM_NO_COMPOSITOR_THREAD) {
		s32 ms_until_next;
		gf_sc_draw_frame(term->compositor, 0, &ms_until_next);
		if ((ms_until_next>=0) && ((u32) ms_until_next > dec_time)) {
			sleep_time = ms_until_next - dec_time;
		}
	} else {
		if (dec_time < term->frame_duration) {
			sleep_time = term->frame_duration - dec_time;
		}
	}

	if (term->bench_mode || (term->user->init_flags & GF_TERM_NO_REGULATION)) return sleep_time;

	assert((s32) sleep_time >= 0);
	if (sleep_time>33) sleep_time = 33;

	gf_sleep(sleep_time);
	return sleep_time;
}

GF_EXPORT
GF_Err gf_term_process_flush(GF_Terminal *term)
{
	u32 i;
	CodecEntry *ce;
	u32 diff, now = gf_sys_clock();
	if (!(term->flags & GF_TERM_NO_COMPOSITOR_THREAD) ) return GF_BAD_PARAM;

	/*update till frame mature*/
	while (1) {

		gf_term_handle_services(term);
		if (term->flags & GF_TERM_NO_DECODER_THREAD) {
			gf_mx_p(term->mm_mx);
			i=0;
			while ((ce = (CodecEntry*)gf_list_enum(term->codecs, &i))) {
				gf_codec_process(ce->dec, 10000);
			}
			gf_mx_v(term->mm_mx);
		}

		if (!gf_sc_draw_frame(term->compositor, 1, NULL)) {
			if (!term->root_scene || !term->root_scene->root_od)
				break;

			if (gf_list_count(term->media_queue) )
				continue;

			//wait for audio to be flushed
			if (gf_sc_check_audio_pending(term->compositor) )
				continue;

			//force end of buffer
			if (gf_scene_check_clocks(term->root_scene->root_od->net_service, term->root_scene, 1))
				break;

			//consider timeout after 30 s
			diff = gf_sys_clock() - now;
			if (diff>30000) {
				GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[Terminal] Waited more than %d ms to flush frame - aborting\n", diff));
				return GF_IP_UDP_TIMEOUT;
			}
		}

		if (! (term->user->init_flags & GF_TERM_NO_REGULATION))
			break;
	}
	return GF_OK;
}

GF_EXPORT
GF_Err gf_term_process_flush_video(GF_Terminal *term)
{
	if (!(term->flags & GF_TERM_NO_COMPOSITOR_THREAD) ) return GF_BAD_PARAM;
	gf_sc_flush_video(term->compositor);
	return GF_OK;
}
